/*
 * frame.c
 *
 *  Created on: 2012-6-27
 *  Author: monkey
 *
 */


#include <vm/frame.h>
#include "threads/thread.h"
#include "threads/interrupt.h"
#include "vm/swap.h"

#define FTE_size (sizeof(struct FTE))
#define FTE_page_contain (PGSIZE/FTE_size)
static void *allocated_page = NULL;
static unsigned int free_offset = 0;
static struct list_elem *oldest_frame;

void
frame_init(void)
{
    list_init(&frame_table);
    lock_init(&frame_table_lock);
    oldest_frame = list_head(&frame_table);
}

bool regist_frame(uintptr_t frame_n, uintptr_t page_n)
{

    struct FTE *f;
    lock_acquire(&frame_table_lock);


    f = find_frame(frame_n);

    if (f == NULL)
    {
        if (allocated_page == NULL)
            allocated_page = palloc_get_page(0);

        if (free_offset >= FTE_page_contain)
        {
            allocated_page = palloc_get_page(0);
            free_offset = 0;
        }
        //printf("**********%d\n",page_n);
        if (allocated_page == NULL)
        {
            lock_release(&frame_table_lock);
            return false;
        }

        f = (struct FTE *)(allocated_page + FTE_size * free_offset);
        free_offset++;

        //lock_init(&f->lock);
        f->pin_num = 0;
        f->frame_number=frame_n;
        f->owner_page_number=page_n;
        f->owner_thread=thread_current();
        list_push_back(&frame_table,&f->elem);

        lock_release(&frame_table_lock);
        return true;
    }
    //}
    else if (f->owner_thread == NULL)
    {
        f->owner_thread = thread_current();
        f->owner_page_number = page_n;

        lock_release(&frame_table_lock);
        return true;
    }
    else
    {
        lock_release(&frame_table_lock);
        return false;
    }
}

bool
unregist_frame(uintptr_t frame_n)
{
    struct FTE *fte;

    lock_acquire(&frame_table_lock);

    fte = find_frame(frame_n);
    if(fte == NULL)
    {
        lock_release(&frame_table_lock);
        return false;
    }
    else
    {
//        fte->owner_page_number=NULL;
        fte->owner_thread=NULL;
    }
    lock_release(&frame_table_lock);
    return true;
}

struct FTE *
find_frame(uintptr_t frame_n)
{
    struct list_elem *e;
    struct FTE *f = NULL;

    for (e = list_begin (&frame_table); e != list_end (&frame_table);
         e = list_next (e))
      {
        f = list_entry (e, struct FTE, elem);

        if(f->frame_number == frame_n)
        {
            break;
        }
      }

    if(e == list_end(&frame_table))
    {
        return NULL;
    }

    return f;
}

void *falloc_get_frame(bool zero)
{
    void *kernel_page;

    //lock_acquire(&frame_table_lock);
    if (zero)
        kernel_page = palloc_get_page(PAL_USER | PAL_ZERO);
    else
        kernel_page = palloc_get_page(PAL_USER);

    //regist_frame(pg_no(kernel_page),0);
    //lock_release(&frame_table_lock);
    return kernel_page;
}

bool falloc_free(void *addr)
{
    palloc_free_page(addr);
    return true;
}

struct FTE *select_frame()
{
    struct FTE *fte;
    //enum intr_level old_level = intr_disable();
    lock_acquire(&frame_table_lock);

    while(true)
    {
        oldest_frame = list_next(oldest_frame);
        if (oldest_frame == list_end(&frame_table))
            oldest_frame = list_begin(&frame_table);

        fte = list_entry(oldest_frame,struct FTE,elem);

        if (fte->owner_thread == NULL)
        {
            lock_release(&frame_table_lock);
            return fte;
        }

        if (fte->pin_num == 0 )
        {
            if (pagedir_is_accessed(fte->owner_thread->pagedir,fte->owner_page_number))
                pagedir_set_accessed(fte->owner_thread->pagedir,fte->owner_page_number,false);
            else
                break;
        }
    }

    //intr_set_level(old_level);
    lock_release(&frame_table_lock);
    return fte;
}

void frame_unregist_all(struct thread *t)
{
    struct list_elem *e;
    lock_acquire(&frame_table_lock);

    for (e = list_begin(&frame_table); e!=list_end(&frame_table); e=list_next(e))
    {
        struct FTE *f;
        f = list_entry (e,struct FTE,elem);

        if (f->owner_thread == t)
            f->owner_thread = NULL;
    }

    lock_release(&frame_table_lock);
}

bool pin_frame(uintptr_t frame_n)
{
    struct FTE* f = find_frame(frame_n);

    if (f == NULL)
        return false;

    f->pin_num++;
    return true;
}

bool unpin_frame(uintptr_t frame_n)
{
    struct FTE* f = find_frame(frame_n);

    if (f == NULL)
        return false;

    if (f->pin_num <= 0)
        return false;

    f->pin_num--;
    return true;
}

bool force_pin(void *vaddr)
{
    //enum intr_level old_level = intr_disable();
//printf("vaddr----------%p\n",vaddr);
    volatile char a = *(char *)vaddr;
//printf("impossible\n");
    struct SPTE *spte = SPTE_find(&thread_current()->SPT,pg_no(vaddr));

    find_frame(spte->frame_number)->pin_num++;

    //intr_set_level(old_level);

    return true;
}

bool unpin_page(uintptr_t page_n)
{
    struct SPTE *spte = SPTE_find(&thread_current()->SPT,page_n);

    find_frame(spte->frame_number)->pin_num--;

    return true;
}
